// SpinTester.cpp: implementation of the CSpinTester class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "AUSlotsTester.h"
#include "SpinTester.h"

#include "AUSlotsOptions.h"
#include "AUSlotsSpinTestClientData.h"
#include "AUSlotsGameModes.h"
#include "AUSlotsGutsPayouts.h"
#include "AUSlotsBonusGameTypes.h"
#include "AUSlotsSpinTestSubstNoteIDToIconID.h"

//#ifdef _DEBUG
//#undef THIS_FILE
//static char THIS_FILE[]=__FILE__;
//#define new DEBUG_NEW
//#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CSpinTester::CSpinTester()
{
	m_pSocket = NULL;
	m_nSlotGutID = 0;
	m_nPort = 0;
	m_hWorkerThread = NULL;
	m_hWorkerThreadSignal = NULL;
	m_hMainThreadSignal = NULL;
	m_eWorkerThreadCommand = wtcInvalid;
	m_bWorkerThreadRV = false;
	m_nNumSpinsPending = 0;
	m_nNumLines = 0;
	m_nBetPerLine = 0;
	m_dwMainThreadID = GetCurrentThreadId();
	m_pSession = NULL;
	m_bResultsReady = false;
	m_bResultsFailed = false;

}

CSpinTester::~CSpinTester()
{
	if ( m_hWorkerThread )
	{
		m_eWorkerThreadCommand = wtcStopThread;

		SetEvent( m_hWorkerThreadSignal );

		WaitForSingleObject( m_hWorkerThread, INFINITE );
	}

	if ( m_hWorkerThreadSignal )
	{
		CloseHandle( m_hWorkerThreadSignal );
	}

	if ( m_hMainThreadSignal )
	{
		CloseHandle( m_hMainThreadSignal );
	}
}

bool CSpinTester::LoadMachineID( CSpinTestInitializeMessage& msgInit )
{
	CAUSlotsOptions options;

	options.m_gutsid = m_nSlotGutID;

	if ( FAILED( options.Open( m_pSession ) ) )
	{
		return false;
	}

	if ( options.MoveNext() != S_OK )
	{
		return false;
	}

	msgInit.m_byMachineID = options.m_machId[0];
	msgInit.m_byMachineSubID = options.m_machSubId;

	return true;
}

bool CSpinTester::InitializeSocket()
{
	// This has to be created from the heap so that it will be associated
	// with the worker thread.
	m_pSocket = new CSocket;

	if ( !m_pSocket )
	{
		return false;
	}

	if ( !m_pSocket->Create() )
	{
		return false;
	}

	if ( !m_pSocket->Connect( m_strServer, m_nPort ) )
	{
		return false;
	}

	return true;
}

bool CSpinTester::LoadSubstNoteID( CSpinTestInitializeMessage& msgInit )
{
	CAUSlotsSpinTestSubstNoteIDToIconID substNote;

	substNote.m_gutsid = m_nSlotGutID;

	if ( FAILED( substNote.Open( m_pSession ) ) )
	{
		return false;
	}

	if ( substNote.MoveNext() == S_OK )
	{
		msgInit.m_bySubstNoteID = substNote.m_noteid;
	}
	else
	{
		msgInit.m_bySubstNoteID = (BYTE)-1;
	}

	return true;
}

bool CSpinTester::LoadNumClientDataSets( CSpinTestInitializeMessage& msgInit )
{
	CAUSlotsSpinTestNumClientDataSets numDataSets;

	numDataSets.m_gutsid = m_nSlotGutID;

	if ( FAILED( numDataSets.Open( m_pSession ) ) )
	{
		return false;
	}

	if ( numDataSets.MoveNext() == S_OK )
	{
		msgInit.m_byNumClientDataSets = (BYTE) numDataSets.m_NumDataSets;
	}
	else
	{
		msgInit.m_byNumClientDataSets = 0;
	}

	return true;
}

bool CSpinTester::LoadServerInitParams( CSpinTestInitializeMessage& msgInit )
{
	msgInit.m_byMaxGameMode = (BYTE)( m_Results.GetNumGameModes() - 1 );
	msgInit.m_byMaxPayoutType = (BYTE)( m_Results.GetNumPayoutTypes() - 1 );
	msgInit.m_byMaxBonusGameType = (BYTE)( m_Results.GetNumBonusGameTypes() - 1 );

	if ( !LoadSubstNoteID( msgInit ) )
	{
		return false;
	}

	if ( !LoadNumClientDataSets( msgInit ) )
	{
		return false;
	}

	return true;
}

bool CSpinTester::SendPacketToServer( void* pPacketData, int nPacketSize )
{
	WORD wSize = (WORD)( sizeof(wSize) + nPacketSize );

	if ( m_pSocket->Send( &wSize, 2 ) == SOCKET_ERROR )
	{
		return false;
	}

	if ( m_pSocket->Send( pPacketData, nPacketSize ) == SOCKET_ERROR )
	{
		return false;
	}

	return true;
}

bool CSpinTester::ReceivePacketFromServer()
{
	BYTE* pCurrentPosition = m_byReceiveBuffer;
	WORD* pSize = (WORD*)m_byReceiveBuffer;
	int nRemainingSpace = sizeof(m_byReceiveBuffer);
	int nTotalRead = 0;

	// Read the packet into the buffer.
	for ( int nRead = m_pSocket->Receive( pCurrentPosition, nRemainingSpace );
		  nRead > 0;
		  nRead = m_pSocket->Receive( pCurrentPosition, nRemainingSpace ) )
	{
		// Keep up with the current status of the buffer.
		nRemainingSpace -= nRead;
		pCurrentPosition += nRead;
		nTotalRead += nRead;

		if ( nTotalRead >= sizeof(*pSize) && nTotalRead == *pSize )
		{
			// We have the whole packet in the receive buffer.
			return true;
		}

		// Avoid buffer overruns.
		if ( nRemainingSpace == 0 )
		{
			// If we get a packet that is bigger than our receive buffer then
			// just ignore it.  Any valid packet that we support should be
			// much smaller than our buffer.
			return false;
		}

		// If this ASSERT fires then we have a buffer overrun problem some how.
		ASSERT( nRemainingSpace > 0 );
	}

	return false;
}

bool CSpinTester::ReceiveAckMessage()
{
	if ( !ReceivePacketFromServer() )
	{
		ASSERT( false );
		return false;
	}

	CSpinTestAckMessage* pMsg = ( CSpinTestAckMessage* )( m_byReceiveBuffer + 2 );

	if ( pMsg->m_byMessageCode != 'T' )
	{
		ASSERT( false );
		return false;
	}

	if ( pMsg->m_byMessageSubCode != CSpinTestMessage::mscAck )
	{
		ASSERT( false );
		return false;
	}

	if ( !pMsg->m_bSuccess )
	{
		ASSERT( false );
		return false;
	}

	return true;
}

bool CSpinTester::SendClientData()
{
	CAUSlotsSpinTestClientDataSets datasets;

	datasets.m_gutsid = m_nSlotGutID;

	if ( FAILED( datasets.Open( m_pSession ) ) )
	{
		return false;
	}

	while ( datasets.MoveNext() == S_OK )
	{
		BYTE buf[256];
		CSpinTestClientDataMessage* pmsgClientData = new( buf ) CSpinTestClientDataMessage;

		CAUSlotsSpinTestClientData data;

		data.m_gutsid = m_nSlotGutID;
		data.m_dataset = datasets.m_dataset;
		data.m_bonusgametype = datasets.m_bonusgametype;

		if ( FAILED( data.Open( m_pSession ) ) )
		{
			return false;
		}

		int n = 0;

		while ( data.MoveNext() == S_OK )
		{
			pmsgClientData->m_adwData[ n++ ] = data.m_datavalue;
		}

		pmsgClientData->m_byBonusGameType = datasets.m_bonusgametype;
		pmsgClientData->m_byDataCount = (BYTE) n;

		int nPacketSize = sizeof(*pmsgClientData);

		nPacketSize += ( n * sizeof( pmsgClientData->m_adwData[0] ) );

		if ( !SendPacketToServer( pmsgClientData, nPacketSize ) )
		{
			return false;
		}

		if ( !ReceiveAckMessage() )
		{
			return false;
		}
	}

	return true;
}

bool CSpinTester::InitializeServer()
{
	if ( !InitializeSocket() )
	{
		return false;
	}

	if ( !m_Results.Initialize( m_pSession, m_nSlotGutID ) )
	{
		return false;
	}

	CSpinTestInitializeMessage msgInit;

	if ( !LoadMachineID( msgInit ) )
	{
		return false;
	}

	if ( !LoadServerInitParams( msgInit ) )
	{
		return false;
	}

	if ( !SendPacketToServer( &msgInit, sizeof( msgInit ) ) )
	{
		return false;
	}

	if ( !ReceiveAckMessage() )
	{
		return false;
	}

	if ( !SendClientData() )
	{
		return false;
	}

	return true;
}

bool CSpinTester::SpinServer()
{
	CSpinTestExecuteSpinsMessage msgSpin;

	msgSpin.m_nBetPerLine = m_nBetPerLine;
	msgSpin.m_nNumLines = m_nNumLines;
	msgSpin.m_nNumSpins = m_nNumSpinsPending;

	if ( !SendPacketToServer( &msgSpin, sizeof( msgSpin ) ) )
	{
		return false;
	}

	return true;
}

bool CSpinTester::ReceiveSummaryData()
{
	ReceivePacketFromServer();

	CSpinTestSummaryDataMessage* pMsg = ( CSpinTestSummaryDataMessage* )( m_byReceiveBuffer + 2 );

	if ( pMsg->m_byMessageCode != 'T' )
	{
		ASSERT( false );
		return false;
	}

	if ( pMsg->m_byMessageSubCode != CSpinTestMessage::mscSummaryData )
	{
		ASSERT( false );
		return false;
	}

	m_Results.AccumulateResults( pMsg );

	return true;
}

bool CSpinTester::SendGameModeSummaryDataRequest( BYTE byGameMode )
{
	CSpinTestGetGameModeSummaryDataMessage msg;

	msg.m_byGameMode = byGameMode;
	
	if ( !SendPacketToServer( &msg, sizeof( msg ) ) )
	{
		return false;
	}

	return true;
}

bool CSpinTester::ReceiveGameModeSummaryData( BYTE byGameMode )
{
	ReceivePacketFromServer();

	CSpinTestGameModeSummaryDataMessage* pMsg = ( CSpinTestGameModeSummaryDataMessage* )( m_byReceiveBuffer + 2 );

	if ( pMsg->m_byMessageCode != 'T' )
	{
		ASSERT( false );
		return false;
	}

	if ( pMsg->m_byMessageSubCode != CSpinTestMessage::mscGameModeSummaryData )
	{
		ASSERT( false );
		return false;
	}

	m_Results.AccumulateResults( byGameMode, pMsg );

	return true;
}

bool CSpinTester::SendPayoutTypeDetailDataRequest( BYTE byGameMode, BYTE byPayoutType )
{
	CSpinTestGetPayoutTypeDetailDataMessage msg;

	msg.m_byGameMode = byGameMode;
	msg.m_byPayoutType = byPayoutType;
	
	if ( !SendPacketToServer( &msg, sizeof( msg ) ) )
	{
		return false;
	}

	return true;
}

bool CSpinTester::ReceivePayoutTypeDetailData( BYTE byGameMode, BYTE byPayoutType )
{
	ReceivePacketFromServer();

	CSpinTestPayoutTypeDetailDataMessage* pMsg = ( CSpinTestPayoutTypeDetailDataMessage* )( m_byReceiveBuffer + 2 );

	if ( pMsg->m_byMessageCode != 'T' )
	{
		ASSERT( false );
		return false;
	}

	if ( pMsg->m_byMessageSubCode != CSpinTestMessage::mscPayoutTypeDetailData )
	{
		ASSERT( false );
		return false;
	}

	m_Results.AccumulateResults( byGameMode, byPayoutType, pMsg );

	return true;
}

bool CSpinTester::SendBonusGameTypeDetailDataRequest( BYTE byBonusGameType )
{
	CSpinTestGetBonusGameTypeDetailDataMessage msg;

	msg.m_byBonusGameType = byBonusGameType;
	
	if ( !SendPacketToServer( &msg, sizeof( msg ) ) )
	{
		return false;
	}

	return true;
}

bool CSpinTester::ReceiveBonusGameTypeDetailData( BYTE byBonusGameType )
{
	ReceivePacketFromServer();

	CSpinTestBonusGameTypeDetailDataMessage* pMsg = ( CSpinTestBonusGameTypeDetailDataMessage* )( m_byReceiveBuffer + 2 );

	if ( pMsg->m_byMessageCode != 'T' )
	{
		ASSERT( false );
		return false;
	}

	if ( pMsg->m_byMessageSubCode != CSpinTestMessage::mscBonusGameTypeDetailData )
	{
		ASSERT( false );
		return false;
	}

	m_Results.AccumulateResults( byBonusGameType, pMsg );

	return true;
}

bool CSpinTester::ReceiveSpinResults()
{
	if ( !ReceiveSummaryData() )
	{
		return false;
	}

	for ( int n = 0; n < m_Results.GetNumGameModes(); n++ )
	{
		if ( !SendGameModeSummaryDataRequest( (BYTE) n ) )
		{
			return false;
		}

		if ( !ReceiveGameModeSummaryData( (BYTE) n ) )
		{
			return false;
		}

		for ( int m = 0; m < m_Results.GetNumPayoutTypes(); m++ )
		{
			if ( !SendPayoutTypeDetailDataRequest( (BYTE) n, (BYTE) m ) )
			{
				return false;
			}

			if ( !ReceivePayoutTypeDetailData( (BYTE) n, (BYTE) m ) )
			{
				return false;
			}
		}
	}

	for (int n = 0; n < m_Results.GetNumBonusGameTypes(); n++ )
	{
		if ( !SendBonusGameTypeDetailDataRequest( (BYTE) n ) )
		{
			return false;
		}

		if ( !ReceiveBonusGameTypeDetailData( (BYTE) n ) )
		{
			return false;
		}
	}

	return true;
}

DWORD CSpinTester::WorkerThreadProc()
{
	while ( WaitForSingleObject( m_hWorkerThreadSignal, INFINITE ) == WAIT_OBJECT_0 )
	{
		m_bWorkerThreadRV = false;

		if ( m_eWorkerThreadCommand == wtcInitializeServer )
		{
			if ( InitializeServer() )
			{
				m_bWorkerThreadRV = true;
			}

			SetEvent( m_hMainThreadSignal );
		}
		else if ( m_eWorkerThreadCommand == wtcSpinServer )
		{
			bool bServerSpinning = false;

			if ( SpinServer() )
			{
				m_bWorkerThreadRV = true;
				bServerSpinning = true;
			}

			SetEvent( m_hMainThreadSignal );

			if ( bServerSpinning && ReceiveSpinResults() )
			{
				m_nNumSpinsPending = 0;
				m_bResultsReady = true;
			}
			else
			{
				m_nNumSpinsPending = 0;
				m_bResultsReady = true;
				m_bResultsFailed = true;
			}
		}
		else if ( m_eWorkerThreadCommand == wtcStopThread )
		{
			delete m_pSocket;
			break;
		}
	}

	return 0;
}

bool CSpinTester::SendCommandToWorkerThread( WorkerThreadCommand wtc )
{
	m_eWorkerThreadCommand = wtc;

	SetEvent( m_hWorkerThreadSignal );

	if ( WaitForSingleObject( m_hMainThreadSignal, INFINITE ) == WAIT_OBJECT_0 )
	{
		return m_bWorkerThreadRV;
	}

	return false;
}

bool CSpinTester::InitializeWorkerThread()
{
	if ( m_hWorkerThreadSignal == NULL )
	{
		m_hWorkerThreadSignal = CreateEvent( NULL, FALSE, FALSE, NULL );

		if ( m_hWorkerThreadSignal == NULL )
		{
			return false;
		}
	}

	if ( m_hMainThreadSignal == NULL )
	{
		m_hMainThreadSignal = CreateEvent( NULL, FALSE, FALSE, NULL );

		if ( m_hMainThreadSignal == NULL )
		{
			return false;
		}
	}

	if ( m_hWorkerThread == NULL )
	{
		DWORD dwWorkerThreadID = 0;

		m_hWorkerThread = CreateThread( NULL, 0, _ThreadProc, this, 0, &dwWorkerThreadID );

		if ( m_hWorkerThread == NULL )
		{
			return false;
		}
	}

	return true;
}

bool CSpinTester::Initialize( CSession* pSession, int nSlotGutID, LPCTSTR szServer, UINT nPort )
{
	m_pSession = pSession;
	m_nSlotGutID = nSlotGutID;
	m_strServer = szServer;
	m_nPort = nPort;

	if ( !InitializeWorkerThread() )
	{
		return false;
	}

	if ( !SendCommandToWorkerThread( wtcInitializeServer ) )
	{
		return false;
	}

	return true;
}

bool CSpinTester::Spin( DWORD nNumSpins, DWORD nNumLines, DWORD nBetPerLine )
{
	if ( m_nNumSpinsPending != 0 )
	{
		return false;
	}

	m_nNumSpinsPending = nNumSpins;
	m_nNumLines = nNumLines;
	m_nBetPerLine = nBetPerLine;

	ResetResults();

	if ( !SendCommandToWorkerThread( wtcSpinServer ) )
	{
		return false;
	}

	return true;
}

bool CSpinTester::IsSpinning() const
{
	return m_nNumSpinsPending != 0;
}

bool CSpinTester::AreResultsReady() const
{
	return m_bResultsReady;
}

bool CSpinTester::DidResultsFail() const
{
	return m_bResultsFailed;
}

const CSpinTestResults* CSpinTester::GetResults() const
{
	return &m_Results;
}

void CSpinTester::ResetResults()
{
	m_bResultsReady = false;
	m_bResultsFailed = false;
	m_Results.Reset();
}
